﻿/*----------------------------------------------------------------
// 公司名称：公司名称
//
// 产品名称：Gaf
//
// 创 建 人：netcasewqs
// 创建日期：2022/3/4
// 邮    箱：netcasewqs@qq.com
//
// Copyright (C) 2022 Gaf，保留所有权利。
//
//----------------------------------------------------------------*/

using Gaf.Impl.Activators;

using System;

namespace Gaf.Impl;

internal class SystemAddinActivator
{
    private readonly string[] _zipFiles = Array.Empty<string>();
    private readonly List<string> _errorMessages = new();
    private readonly List<AddinDescriptor> _AddinDescriptors = new();
    public List<string> SkipDlls { get; private set; }
    public List<string> AddinDLLs { get; private set; }

    private readonly GafImpl _framework;

    private GafOptions Descriptor => _framework.Options;
    internal AddinRepository Repository => _framework._repository;

    public SystemAddinActivator(GafImpl framework)
    {
        _framework = framework;
        SkipDlls = new List<string>();
        AddinDLLs = new List<string>();
        InitSkipDlls();

        if (Descriptor.PackageFormat.HasFlag(AddinPackageFormat.Directory)) // 如果是目录插件，那么就创建插件根目录
        {
            CreateAddinsDirectory(Descriptor.AddinsDirectory);
            if (Descriptor.IsGpk)
                _zipFiles = Directory.GetFiles(Descriptor.AddinsDirectory, $"*{Descriptor.PackageSuffixName}", SearchOption.TopDirectoryOnly);
        }
    }

    public void Configure(IServiceCollection? services, IConfiguration? configuration)
    {
        //安装所有插件
        try
        {
            _framework.FireFrameStateChanged(FrameworkState.Installing);

            IntstallAddinsCore();
        }
        catch (Exception ex)
        {
            _framework.FireErrorOccurred(ex);
            if (_framework.Options.AutoExitOnStartAddinError)
                throw;
        }
        finally
        {
            _framework.FireFrameStateChanged(FrameworkState.Installed);
        }

        try
        {
            _framework.FireFrameStateChanged(FrameworkState.Configuring);

            IServiceCollection? sourceServices = services;
            //配置插件
            foreach (AddinActivator activator in Repository.Activators.OrderBy(p => p.Addin.StartLevel))
            {
                activator._classLoader.SafeExecute(() =>
                {
                    try
                    {
                        if (sourceServices != null) services = new GafServiceCollection(sourceServices, activator.Addin._Services);

                        activator.Configure(services, configuration);
                    }
                    catch (Exception e)
                    {
                        var message = $"Addin {activator.Addin} cannot be {nameof(Configure)}, because configure error:" + e.Message;
                        Log.Error(message, e);

                        if (_framework.Options.AutoExitOnStartAddinError)
                            throw new ApplicationException(message, e);
                        else
                            _framework.FireErrorOccurred(e);
                    }
                });
            }
        }
        catch (Exception ex)
        {
            _framework.FireErrorOccurred(ex);
            throw;
        }
        finally
        {
            _framework.FireFrameStateChanged(FrameworkState.Configured);
        }
    }

    public void Start(IServiceProvider serviceProvider)
    {
        try
        {
            _framework.FireFrameStateChanged(FrameworkState.Starting);

            foreach (AddinActivator addin in Repository.Activators.OrderBy(p => p.Addin.StartLevel))
            {
                addin._classLoader.SafeExecute(() =>
                {
                    bool canStart = true;

                    try
                    {
                        canStart = addin.Match(serviceProvider);
                    }
                    catch (Exception e)
                    {
                        var message = $"Addin {addin.Addin} cannot be {nameof(AddinActivator.Match)}, because match error:" + e.Message;
                        Log.Error(message);
                        if (_framework.Options.AutoExitOnStartAddinError)
                            throw new ApplicationException(message, e);
                    }

                    if (!canStart)
                    {
                        Log.Warn($"Addin[{addin.Addin.Id}-'{addin.Addin.Name}'] match result:失败.");
                    }
                    else
                    {
                        try
                        {
                            addin.Start(serviceProvider);
                        }
                        catch (Exception e)
                        {
                            var message = $"Addin {addin.Addin} cannot be {nameof(AddinActivator.Start)}, because start error:" + e.Message;
                            Log.Error(message);
                            if (_framework.Options.AutoExitOnStartAddinError)
                                throw new ApplicationException(message, e);
                        }
                    }
                  
                });
            }
        }
        catch (Exception ex)
        {
            _framework.FireErrorOccurred(ex);
            throw;
        }
        finally
        {
            _framework.FireFrameStateChanged(FrameworkState.Started);
        }
    }

    public void Uninstall(IServiceProvider? serviceProvider)
    {
        foreach (AddinActivator addin in Repository.Activators.OrderByDescending(p => p.Addin.StartLevel))
        {
            try
            {
                addin.Uninstall(serviceProvider);
            }
            catch (Exception e)
            {
                if (_framework.Options.AutoExitOnStartAddinError)
                    throw new ApplicationException($"Addin {addin.Addin} cannot be {nameof(AddinActivator.Uninstall)}, because uninstall error:" + e.Message, e);
                else
                    _framework.FireErrorOccurred(e);
            }
        }
    }

    #region 私有方法

    private void IntstallAddinsCore()
    {
        if (Descriptor.PackageFormat.HasFlag(AddinPackageFormat.Lite))
        {
            InstallLiteAddins();
        }
        if (Descriptor.PackageFormat.HasFlag(AddinPackageFormat.Directory))
        {
            if (Descriptor.IsGpk)
                UncompressAddins();
            InstallCategoryAddins();
            void InstallCategoryAddins()
            {
                var AddinDescriptors = AddinDescriptorLoader.Load(Descriptor.AddinsDirectory, _errorMessages);
                if (AddinDescriptors != null && AddinDescriptors.Any())
                {
                    foreach (var description in AddinDescriptors)
                    {
                        if (!_framework.CanInstall(description))
                        {
                            Log.Info($@"禁用 {description.Name} 安装,如果想恢复安装，请检查:
                                        1. gaf.ini 文件的DisableAddins 的配置中是否被禁止
                                        2. 插件的manifest.ini 文件的Enable=false
                                        3. 插件的启动级别 是否大于框架的启动");
                            continue;
                        }
                        _AddinDescriptors.Add(description);

                        if (string.IsNullOrEmpty(description.Name))
                        {
                            Log.Warn($"插件名称为空，不允许安装,详细信息:{description} ");
                            continue;
                        }
                        var addin = Repository.Activators.FirstOrDefault(p => p.Addin.Name == description.Name);
                        if (addin != null)
                        {
                            var message = $"插件:{description.Name} 已存在";
                            Log.Error(message);
                            _errorMessages.Add(message);
                        }
                        else
                        {
                            var (addin2, errorMessage) = InstallAddin(description);
                            if (errorMessage.Count == 0)
                            {
                                Repository.Register(addin2);
                                _framework.FireAddinChanged(addin2, AddinState.Installed);
                            }
                            else
                            {
                                try
                                {
                                    Log.Error($"插件:{description.Name} 安装失败,错误信息:{string.Join(Environment.NewLine, errorMessage)}");
                                    //如果插件加载失败则卸载
                                    addin2._classLoader.Unload();
                                }
                                catch
                                {
                                }
                            }
                        }
                    }
                }

                if (_errorMessages.Count > 0)
                {
                    throw new AddinException(AddinExceptionType.FrameworkInitError, String.Join(Environment.NewLine, _errorMessages));
                }
            }
        }
    }

    private static List<LiteActivatorDescriptor> GetLiteActivatorDescriptors(IEnumerable<Assembly> assemblies) =>
        (from asm in assemblies
         where asm.IsCustomAssembly()
         from t in asm.GetTypes()
         select LiteActivatorDescriptor.ParseLiteActivatorDescriptor(t) into desc
         where desc != null
         select desc).ToList();

    private static List<ActivatorDescriptor> GetActivatorDescriptors(ClassLoader loadContext) =>
      (from asm in loadContext.Assemblies
       where loadContext.IsCustomAssembly(asm)
       from t in asm.GetTypes()
       select ActivatorDescriptor.ParseActivatorDescriptor(t) into desc
       where desc != null
       select desc).ToList();

    private void InstallLiteAddins()
    {
        var activatorDescriptors = GetLiteActivatorDescriptors(AssemblyLoadContext.Default.Assemblies.ToArray());
        var activators = activatorDescriptors
            .Select(p => new LiteDelegateActivator(p))
            .Where(p => _framework.CanInstall(p.AddinDescriptor))
            .OrderBy(p => p.AddinDescriptor.StartLevel)
            .ToList();

        foreach (var addinActivator in activators)
        {
            addinActivator.AddinDescriptor.Location = _framework.Options.AddinsDirectory;

            var addin = new AddinActivator(_framework, addinActivator.AddinDescriptor, isLiteAddin: true)
            {
                InnerActivator = addinActivator
            };

            Repository.Register(addin);
            _framework.FireAddinChanged(addin, AddinState.Installed);
        }
    }

    /// <summary>
    /// 初始化跳过的DLL 组件
    /// </summary>
    private void InitSkipDlls()
    {
        #region 跳过1: 程序目录下 以单独dll 出现

        // 获取主程序 已经存在的（不许在再加载的 dll）
        SkipDlls = new DirectoryInfo(AppContext.BaseDirectory).GetFiles("*.dll").Select(m => m.Name).ToList();

        #endregion 跳过1: 程序目录下 以单独dll 出现

        #region 跳过2: 打包进入1个dll 或 打包进 1个exe

        // 注意: 用户可能将 dll 打包在 一个dll中, 或打包进 exe, 因此 通过此方式 确保跳过
        // 主程序所有 位于 AssemblyLoadContext.Default
        List<string> skipAssembliesName = AssemblyLoadContext.Default.Assemblies
            .Select(m => m.GetName())
            .Select(m => m.Name)
            .Where(m => !string.IsNullOrWhiteSpace(m))
            .Select(m => m!)
            .ToList();
        foreach (var name in skipAssembliesName)
        {
            SkipDlls.Add($"{name}.dll");
        }

        #endregion 跳过2: 打包进入1个dll 或 打包进 1个exe
    }

    private void UncompressAddins()
    {
        if (!Descriptor.IsGpk) return;
        foreach (var zipFile in _zipFiles)
        {
            string AddinStorateDirectory = Path.Combine(Descriptor.AddinsDirectory, zipFile.Replace(Descriptor.PackageSuffixName!, ""));
            if (Directory.Exists(AddinStorateDirectory))
                Directory.Delete(AddinStorateDirectory, true);

            Log.Info($"解压插件：{zipFile} 文件到 {Descriptor.AddinsDirectory}目录下");
            using var zipArchive = ZipFile.Open(zipFile, ZipArchiveMode.Read);
            zipArchive.ExtractToDirectory(Descriptor.AddinsDirectory, true);
        }
    }

    private static void CreateAddinsDirectory(string AddinsDirectory)
    {
        Stack<string> stack = new();
        string text = AddinsDirectory;
        while (!Directory.Exists(text))
        {
            stack.Push(text);
            text = Directory.GetParent(text)!.FullName;
        }
        while (stack.Count > 0)
        {
            var dir = stack.Pop();
            Log.Info($"创建插件目录:{dir}");
            Directory.CreateDirectory(dir);
        }
    }

    private (AddinActivator addin, List<string> errorMessage) InstallAddin(AddinDescriptor addinDescriptor)
    {
        // 此插件的 加载上下文

        var addinActivator = new AddinActivator(_framework, addinDescriptor, isLiteAddin: false);

        List<string> errorMessage = new();

        // 跳过不需要加载的 dll
        string[] skipDlls = SkipDlls.Union(AddinDLLs).ToArray();

        #region 加载插件的dll

        foreach (var (fileName, file) in addinActivator._classLoader._managedLibsPath)
        {
            if (skipDlls.Contains(fileName)) continue;

            try
            {
                addinActivator._classLoader.LoadAssembly(file);
                AddinDLLs.Add(fileName);
            }
            catch (Exception ex)
            {
                errorMessage.Add($"加载{addinDescriptor.Name} 插件的dll:{file}失败，错误信息:{ex.Message}");
            }
        }

        addinActivator.Addin.Assemblies = addinActivator._classLoader.Assemblies;

        #endregion 加载插件的dll

        //加载非托管dll
        foreach (var (fileName, file) in addinActivator._classLoader._unManagedLibsPath)
        {
            if (skipDlls.Contains(fileName)) continue;

            try
            {
                addinActivator._classLoader.LoadUnmanagedDllFromPath(file);
                AddinDLLs.Add(fileName);
            }
            catch (Exception ex)
            {
                errorMessage.Add($"加载{addinDescriptor.Name} 插件引用的非托管dll:{file}失败，错误信息:{ex.Message}");
            }
        }
        var unmanagedDllDir = new DirectoryInfo(Path.Combine(addinDescriptor.Location, Constants.UnmanagedDllPathName));

        var activatorDescriptors = GetActivatorDescriptors(addinActivator._classLoader);
        if (activatorDescriptors.Count > 1)
            errorMessage.Add($"插件的Activator 只能允许有1个，但{addinDescriptor.Name} 插件有多个:{string.Join(",", activatorDescriptors.Select(p => p.Constructor.Constructor.DeclaringType?.Name))}");
        else if (activatorDescriptors.Count == 1)
            addinActivator.InnerActivator = new DelegateActivator(activatorDescriptors[0], addinActivator.Addin);
        else
            errorMessage.Add($"{addinDescriptor.Name} 插件没有定义Activator");

        return (addinActivator, errorMessage);
    }

    #endregion 私有方法
}